﻿using System;
using System.Net.Mail;
using System.Text;
using System.Web;
using System.Web.UI;
using Altairis.Web.Management.Configuration;
using System.IO;

namespace Altairis.Web.Management {
    public class ErrorHandlingModule : IHttpModule {
        private static readonly Type[] IGNORED_EXCEPTION_TYPES = { // Exception types to be ignored
            typeof(HttpRequestValidationException),     // potentially dangerous request...
            typeof(ViewStateException),                 // invalid viewstate
            typeof(HttpException)                       // HTTP exception
        };

        private ManagementSection config;

        public void Dispose() { } // NOOP

        public void Init(HttpApplication context) {
            this.config = ManagementSection.GetCurrentOrDefault();
            if (this.config.ExceptionMailer.Enabled) context.Error += new EventHandler(context_Error);
            if (this.config.ErrorPages.Enabled) context.EndRequest += new EventHandler(context_EndRequest);
        }

        void context_EndRequest(object sender, EventArgs e) {
            var app = sender as HttpApplication;

            // Exclude local requests
            if (config.ErrorPages.ExcludeLocalRequests && app.Request.IsLocal) return;

            // Catch only errors
            if (app.Response.StatusCode < 400 || app.Response.StatusCode == 401 || app.Response.StatusCode > 599) return;

            // Try to find appropriate error page
            var path = this.config.ErrorPages.FolderName;
            if (path.StartsWith("~/")) path = app.Server.MapPath(path);
            var fileName = Path.Combine(path, string.Format("{0}.htm", app.Response.StatusCode));
            if (!File.Exists(fileName)) {
                // Specific page not found - try fallback
                fileName = Path.Combine(path, string.Format("{0}.htm", this.config.ErrorPages.FallbackErrorCode));
                if (!File.Exists(fileName)) return; // giving up - we don't have error page nor fallback
            }

            // Send error file
            app.Response.ClearContent();
            app.Response.TransmitFile(fileName);
            app.Response.End();
        }

        void context_Error(object sender, EventArgs e) {
            // Get HTTP context
            var app = sender as HttpApplication;

            // Exclude ignored URLs and local requests
            if (config.ExceptionMailer.ExcludeSystemUrls && app.Request.IsInfrastructureRequest()) return;
            if (config.ExceptionMailer.ExcludeLocalRequests && app.Request.IsLocal) return;

            // Get exception info
            Exception ex = app.Server.GetLastError();
            if (Array.IndexOf(IGNORED_EXCEPTION_TYPES, ex.GetType()) > -1 || Array.IndexOf(IGNORED_EXCEPTION_TYPES, ex.InnerException.GetType()) > -1 || Array.IndexOf(IGNORED_EXCEPTION_TYPES, ex.GetBaseException().GetType()) > -1) return;
            Exception bx = ex.GetBaseException();

            // Compose mail
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("Topmost exception type: " + ex.GetType().ToString());
            sb.AppendLine("Date and time:          " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
            sb.AppendLine("Requested URL:          " + app.Request.Url.ToString());
            sb.AppendLine("Remote IP address:      " + app.Request.UserHostAddress);
            sb.AppendLine("Authenticated request:  " + (app.Request.IsAuthenticated ? "yes, as " + app.User.Identity.Name : "no"));
            sb.AppendLine("User agent:             " + app.Request.UserAgent);
            sb.AppendLine("Server name:            " + app.Server.MachineName.ToUpper());
            sb.AppendLine();
            sb.AppendLine("-- Exception --------------------------------------------------------------");
            sb.AppendLine(ex.ToString());
            sb.AppendLine();
            sb.AppendLine("-- Cookies ----------------------------------------------------------------");
            if (app.Request.Cookies.Count == 0) {
                sb.AppendLine("(none)");
            }
            else {
                foreach (string key in app.Request.Cookies.AllKeys) {
                    var c = app.Request.Cookies[key];

                    if (c.HasKeys) {
                        sb.AppendLine(key + " = (composite)");
                        foreach (string subKey in c.Values.AllKeys) sb.AppendLine("  " + subKey + " = " + c[subKey]);
                    }
                    else {
                        sb.AppendLine(key + " = " + c.Value);
                    }
                }
            }
            sb.AppendLine();
            sb.AppendLine("-- POST data --------------------------------------------------------------");
            if (app.Request.Form.Count == 0) {
                sb.AppendLine("(none)");
            }
            else {
                foreach (string key in app.Request.Form.AllKeys) {
                    sb.AppendLine(key + " = " + app.Request.Form[key]);
                }
            }
            sb.AppendLine();
            sb.AppendLine("[END OF MESSAGE]");

            // Create mail message
            using (var msg = new MailMessage()) {
                // Set sender and recpient
                msg.From = new MailAddress(
                    config.ExceptionMailer.MailSenderAddress,
                    string.IsNullOrEmpty(config.ExceptionMailer.MailSenderName) ? app.Request.Url.Host : config.ExceptionMailer.MailSenderName);
                msg.To.Add(config.ExceptionMailer.MailRecipientAddress);

                // Set subject and trim if necessary
                if (config.ExceptionMailer.MaximumSubjectLength == 0 || bx.Message.Length < config.ExceptionMailer.MaximumSubjectLength) {
                    msg.Subject = bx.Message;
                }
                else {
                    msg.Subject = bx.Message.Substring(0, config.ExceptionMailer.MaximumSubjectLength - 3) + "...";
                }

                // Set body
                msg.Body = sb.ToString();

                // Send mail
                using (var mx = new SmtpClient()) {
                    if (!string.IsNullOrWhiteSpace(this.config.ExceptionMailer.MailPickupFolder)) {
                        var pickupFolder = this.config.ExceptionMailer.MailPickupFolder.Trim();
                        if (pickupFolder.StartsWith("~") || pickupFolder.StartsWith("/")) pickupFolder = app.Context.Server.MapPath(pickupFolder);
                        mx.DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory;
                        mx.PickupDirectoryLocation = pickupFolder;
                    }
                    mx.Send(msg);
                }
            }
        }

    }
}
